// Copyright (C) 2013 Michael Leviston (michael.leviston@gmail.com)

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#ifndef ECS_WORLD_H
#define ECS_WORLD_H

#include "System.h"

namespace ECS
{
	// Pointer Definitions
	class World;
	typedef std::tr1::shared_ptr<World> spWorld;
	typedef std::tr1::weak_ptr<World> wpWorld;

	/// \brief A world stores, manages and updates entities, components and systems.
	///
	/// \author Michael Leviston
	class World
	{
	public:
		/// Constructor
		World();

		/// Destructor
		~World();

		/// Update all systems managed by the world
		void Update(float dt);

		/// Clear all systems and entities from the world
		void Shutdown();

		/// Add a system to the world
		void AddSystem(spSystem spS);

		/// Get a weak ptr to an entity by Unique ID
		wpEntity GetEntity(unsigned int EntityID);
		
		/// Create a new entity and return the ID to add components
		unsigned int  AddEntity();

		/// Add a strong ptr to component to the entity by ID
		void AddComponent(unsigned int EntityID, spComponent spC);

		/// Register the entity with all the systems in the world
		void ActivateEntity(unsigned int EntityID);

		/// Mark an entity for removal by ID
		bool MarkEntity(unsigned int EntityID);

		/// Mark a component for removal by IDs
		bool MarkComponent(unsigned int EntityID, unsigned int ComponentID);

		/// Get a strong pointer to a system (if it exists - otherwise return empty sPtr)
		template <class T>
		spSystem GetSystem()
		{
			for(auto it = systems.begin(); it != systems.end(); ++it)
			{
				if(it->second->GetType() == T::type)
				{
					return it->second;
				}
			}

			spSystem spS;
			return spS;
		}

		/// Mark a component for removal by type
		template <class T>
		bool MarkComponent(int EntityID)
		{
			auto itF = entities.find(EntityID);
			if(itF != entities.end() )
			{
				if(itF->second->Has<T>() )
				{
					auto spC = ECS::MakeStrongPtr( itF->second->Get<T>() );
					spC->MarkForRemoval();
					markedComponents.insert(std::pair<int,int>(EntityID, spC->GetID() ) );
					return true;
				}
			}

			return false;
		}

	private:

		void UpdateSystems(float dt);

		void RemoveEntities();
		void RemoveComponents();

		void RemoveEntity(unsigned int EntityID);
		void RemoveComponent(unsigned int EntityID, unsigned int ComponentID);

		//--------------------------------------------------------------------------
		// Template Functions
		template <class T>
		void RemoveComponent(unsigned int EntityID)
		{
			auto itF = entities.find(EntityID);
			if(itF != entities.end() )
			{
				unsigned int ComponentID = itF->second->RemoveComponentByType<T>();
				if( ComponentID != 0)
				{
					for(auto itB = systems.begin(); itB != systems.end(); ++itB)
					{
						itB->second->DeleteNode(ComponentID);
					}

					if(itF->second->components.size() == 0)
					{
						itF->second->MarkForRemoval();
						markedEntities.insert(EntityID);
					}
				}
			}
		}

		//--------------------------------------------------------------------------
		// Member Variables
		std::map<unsigned int, spEntity> entities;
		std::map<unsigned int, spSystem> systems;

		std::set<unsigned int> markedEntities;
		std::multimap<unsigned int, unsigned int> markedComponents;

		std::set<unsigned int> unusedEntityIDs;
		std::set<unsigned int> unusedComponentIDs;

		unsigned int eID, cID;
	};

}


#endif // ECS_WORLD_H